use anyhow::anyhow;
use anyhow::ensure;
use anyhow::Result;
use chrono::DateTime;
use chrono::Datelike;
use chrono::Duration;
use chrono::Local;
use chrono::NaiveTime;
use chrono::Utc;
use chrono::Weekday;
use fudata::model::kline::Kline;
use fudata::model::kline::Klt;
use fudata::model::security;
use log::error;

///    行情之间的时间间隔，可选示例如下
///    - ``5``      : 5 分钟
///    - ``15``     : 15分钟
///    - ``30``     : 30分钟
///    - ``60``     : 60分钟
///    - ``101``    : 日
///    - ``102``    : 周
///    - ``103``    : 月
pub fn convert_from_eastmoney_klt_to_fuda_klt(eastmoney_klt: u32) -> Result<Klt> {
    ensure!(
        eastmoney_klt == 5 ||
            eastmoney_klt == 15 ||
            eastmoney_klt == 30 ||
            eastmoney_klt == 60 ||
            eastmoney_klt == 101 ||
            eastmoney_klt == 102 ||
            eastmoney_klt == 103,
        format!("invalid eastmoney_klt: {:?}", eastmoney_klt)
    );

    match eastmoney_klt {
        5 => Ok(Klt::M005),
        15 => Ok(Klt::M015),
        30 => Ok(Klt::M030),
        60 => Ok(Klt::M060),
        101 => Ok(Klt::DAY),
        102 => Ok(Klt::WEEK),
        103 => Ok(Klt::MONTH),
        _ =>
            Err(
                anyhow!(
                    format!(
                        "Unsupport eastmoney_klt={eastmoney_klt}, only 5,15,30,60,101,102,103 supported!"
                    )
                )
            ),
    }
}

///    行情之间的时间间隔，可选示例如下
///    - ``5``      : 5 分钟
///    - ``15``     : 15分钟
///    - ``30``     : 30分钟
///    - ``60``     : 60分钟
///    - ``101``    : 日
///    - ``102``    : 周
///    - ``103``    : 月
pub fn convert_from_fuda_klt_to_eastmoney_klt(fuda_klt: &Klt) -> Result<u32> {
    match fuda_klt {
        Klt::M005 => Ok(5),
        Klt::M015 => Ok(15),
        Klt::M030 => Ok(30),
        Klt::M060 => Ok(60),
        Klt::DAY => Ok(101),
        Klt::WEEK => Ok(102),
        Klt::MONTH => Ok(103),
        _ =>
            Err(
                anyhow!(
                    format!("Unsupport fuda_klt={fuda_klt}, only 5,15,30,60,101,102,103 supported!")
                )
            ),
    }
}

pub struct SecurityKlineWrap {
    pub kline: Kline,
}

impl From<&eastmoney::stock::stock_kline::KlineTick> for SecurityKlineWrap {
    fn from(v: &eastmoney::stock::stock_kline::KlineTick) -> Self {
        let parsed_time = dateparser
            ::parse_with(&v.time, &Utc, NaiveTime::from_hms_opt(0, 0, 0).unwrap())
            .unwrap_or_default();
        // println!("{}=>{}", &v.time, parsed_time);
        SecurityKlineWrap {
            kline: Kline {
                klts: parsed_time,
                open: v.open,
                close: v.close,
                high: v.high,
                low: v.low,
                volume: v.volume,
                value: v.value,
                amplitude: v.amplitude,
                frate: v.fluctuation_rate,
                famount: v.fluctuation_amount,
                trate: v.turnover_rate,
                ..Default::default()
            },
        }
    }
}

///
/// symbol: 513130, 159001, 600519
///

pub(crate) fn get_stock_market(symbol: &str) -> Result<security::Market> {
    if symbol.to_uppercase().starts_with("SH") {
        Ok(security::Market::SH)
    } else if symbol.to_uppercase().starts_with("SZ") {
        Ok(security::Market::SZ)
    } else if symbol.to_uppercase().starts_with("BJ") {
        Ok(security::Market::BJ)
    } else if
        ["50", "51", "60", "68", "90", "110", "113", "132", "204"]
            .iter()
            .any(|&x| symbol.starts_with(x))
    {
        Ok(security::Market::SH)
    } else if
        ["00", "12", "13", "18", "15", "16", "18", "20", "30", "39", "115", "1318"]
            .iter()
            .any(|&x| symbol.starts_with(x))
    {
        Ok(security::Market::SZ)
    } else if
        ["01", "02", "04", "05", "07", "08", "09", "10", "11", "21", "22", "23"]
            .iter()
            .any(|&x| symbol.starts_with(x))
    {
        // todo, need to verify for open fund
        Ok(security::Market::SZ)
    } else if ["5", "6", "9", "7"].iter().any(|&x| symbol.starts_with(x)) {
        Ok(security::Market::SH)
    } else if ["4", "8"].iter().any(|&x| symbol.starts_with(x)) {
        Ok(security::Market::BJ)
    } else {
        // Err(anyhow!(format!(
        //     "Unknown security::Market for symbol: {}",
        //     symbol
        // )))

        error!("Unknown security::Market for symbol: {}, set as SH", symbol);

        Ok(security::Market::SH)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_get_stock_market() {
        assert_eq!(get_stock_market("sh600123").ok(), Some(security::Market::SH));
        assert_eq!(get_stock_market("sH600123").ok(), Some(security::Market::SH));
        assert_eq!(get_stock_market("Sh600123").ok(), Some(security::Market::SH));
        assert_eq!(get_stock_market("SH600123").ok(), Some(security::Market::SH));
        assert_eq!(get_stock_market("600123").ok(), Some(security::Market::SH));
        assert_eq!(get_stock_market("513130").ok(), Some(security::Market::SH));
        assert_eq!(get_stock_market("sz002345").ok(), Some(security::Market::SZ));
        assert_eq!(get_stock_market("002345").ok(), Some(security::Market::SZ));
        assert_eq!(get_stock_market("302345").ok(), Some(security::Market::SZ));
    }

    // Add more test cases here
}

/// Adds the specified duration to the current local datetime.
///
/// # Arguments
///
/// * `days` - Number of days to add.
/// * `hours` - Number of hours to add.
/// * `minutes` - Number of minutes to add.
/// * `seconds` - Number of seconds to add.
///
/// # Returns
///
/// A `DateTime<Local>` representing the new datetime after adding the duration.
pub fn now_plus_date_time(days: i64, hours: i64, minutes: i64, seconds: i64) -> DateTime<Local> {
    let today_midnight = Local::now()
        .date_naive() // Get the date part of the current datetime.
        .and_hms_opt(0, 0, 0) // Set the time to midnight.
        .unwrap_or_default()
        .and_local_timezone(Local) // Convert NaiveDateTime to DateTime<Local>
        .unwrap(); // Handle the potential error here or use expect, etc.

    let total_duration =
        Duration::days(days) +
        Duration::hours(hours) +
        Duration::minutes(minutes) +
        Duration::seconds(seconds);
    today_midnight + total_duration
}

/// Get the date of the previous working weekday (Monday to Friday).
pub fn previous_trading_day(date: &chrono::DateTime<Local>) -> chrono::DateTime<Local> {
    let mut prev_day = date.clone();

    loop {
        prev_day = prev_day - chrono::Duration::days(1);
        if
            [Weekday::Mon, Weekday::Tue, Weekday::Wed, Weekday::Thu, Weekday::Fri].contains(
                &prev_day.weekday()
            )
        {
            break prev_day;
        }
    }
}

/// Get the date of the  next non trading weekday (Sat to Sun).
pub fn next_non_trading_day(date: &chrono::DateTime<Local>) -> chrono::DateTime<Local> {
    let mut next_day = date.clone();

    loop {
        next_day = next_day + chrono::Duration::days(1);
        if [Weekday::Sat, Weekday::Sun].contains(&next_day.weekday()) {
            break next_day;
        }
    }
}

#[cfg(test)]
mod test {
    use chrono::Local;

    use crate::helper::{ now_plus_date_time, previous_trading_day };

    #[tokio::test]
    async fn test_next_date_time() {
        // Example usage:
        let next_datetime = now_plus_date_time(1, 15, 30, 0);
        println!("Next datetime will be: {}", next_datetime);
    }

    #[tokio::test]
    async fn test_previous_trading_day() {
        let today = Local::today().and_hms(0, 0, 0); // Get today's date at midnight
        let prev_tradeday = previous_trading_day(&today);
        println!("Previous trading day: {}", prev_tradeday);
    }
}
